#pragma once

#include "qconf.h"

#ifdef __WIN__

#include <WinSock2.h>
#include <io.h>
#include <windows.h>
#include <ws2ipdef.h>
#include <ws2tcpip.h>

#else

#include <sys/socket.h>  // NOLINT
#include <sys/types.h>   // NOLINT
#include <unistd.h>      // NOLINT

#endif

namespace qio {

#if (defined(__WIN__) || defined(__APPLE__))

#ifndef HAVE_MSG_NOSIGNAL
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
#endif

#endif

#ifdef __WIN__

#define IO_ERR_RW_RETRIABLE(e) \
  ((e) == WSAEWOULDBLOCK || (e) == WSAETIMEDOUT || (e) == WSAEINTR)

#define IO_ERR_CONNECT_RETRIABLE(e)                                     \
  ((e) == WSAEWOULDBLOCK || (e) == WSAEINTR || (e) == WSAEINPROGRESS || \
   (e) == WSAEINVAL)

#define IO_ERR_ACCEPT_RETRIABLE(e) IO_ERR_RW_RETRIABLE(e)

#define IO_ERR_CONNECT_REFUSED(e) ((e) == WSAECONNREFUSED)

#else

/* True iff e is an error that means a read/write operation can be retried. */
#define IO_ERR_RW_RETRIABLE(e) ((e) == EINTR || (e) == EAGAIN)
/* True iff e is an error that means an connect can be retried. */
#define IO_ERR_CONNECT_RETRIABLE(e) ((e) == EINTR || (e) == EINPROGRESS)
/* True iff e is an error that means a accept can be retried. */
#define IO_ERR_ACCEPT_RETRIABLE(e) \
  ((e) == EINTR || (e) == EAGAIN || (e) == ECONNABORTED)

/* True iff e is an error that means the connection was refused */
#define IO_ERR_CONNECT_REFUSED(e) ((e) == ECONNREFUSED)

#endif

void _boom_place_holder_function() QIO_NOEXCEPT;

#ifndef __WIN__
#define INVALID_SOCKET (-1)
#endif

// errcode or WSAGetLastError()
inline int GetLastSocketError() QIO_NOEXCEPT {
#ifdef __WIN__
  return WSAGetLastError();
#else
  return errno;
#endif
}

// close
inline int socket_close(Fd fd) QIO_NOEXCEPT {
#if defined(__APPLE__) || defined(__UNIX__)
  return ::close(fd);
#else
  return ::closesocket(fd);
#endif
}

// accept
inline Fd socket_accept(Fd socket, struct sockaddr *address,
                        socklen_t *address_len) QIO_NOEXCEPT {
  return ::accept(socket, address, address_len);
}

// send
inline ssize_t socket_send(Fd fd, const void *buffer, size_t length,
                           int flags) QIO_NOEXCEPT {
#if !defined(__WIN__)
  return ::send(fd, buffer, length, flags);
#else
  return ::send(fd, reinterpret_cast<const char *>(buffer), length, flags);
#endif
}

// recv
inline ssize_t socket_recv(Fd fd, void *buffer, size_t length,
                           int flags) QIO_NOEXCEPT {
#if !defined(__WIN__)
  return ::recv(fd, buffer, length, flags);
#else
  return ::recv(fd, reinterpret_cast<char *>(buffer), length, flags);
#endif
}

// setsockopt
inline int socket_setsockopt(Fd socket, int level, int option_name,
                             const void *option_value,
                             socklen_t option_len) QIO_NOEXCEPT {
#if !defined(__WIN__)
  return setsockopt(socket, level, option_name, option_value, option_len);
#else
  return ::setsockopt(socket, level, option_name,
                      reinterpret_cast<const char *>(option_value), option_len);
#endif
}

// socket
inline Fd socket_create(int domain, int type, int protocol) QIO_NOEXCEPT {
  return ::socket(domain, type, protocol);
}

// bind
inline int socket_bind(Fd socket, const struct sockaddr *address,
                       socklen_t address_len) QIO_NOEXCEPT {
  return ::bind(socket, address, address_len);
}

// listen
inline int socket_listen(Fd socket, int backlog) QIO_NOEXCEPT {
  return ::listen(socket, backlog);
}

}  // namespace qio